﻿/**
 * @brief INI 文件读取解析
 * @version 1.1
 * @author 宋炜
 * @date 2021-7-16 ~ 2023-7-14
 */

/// 2023-7-14  增加修改变量功能
/// 2023-7-14  增加保存文件功能

#pragma once

#include <string>
#include <sstream>
#include <type_traits>
#include <map>

#include "ary_str.hpp"
#include "mallocSharedPtr.hpp"
namespace wheels{
class iniRead
{
public:
	enum emErrCode{
		ERR_ALLOC_MEM = -1000,
		ERR_CAN_NOT_FIND_KEY,
		ERR_CAN_NOT_FIND_SECTION,
		ERR_EMPTY_FILE,
		ERR_SECTION_NOT_EXIST,
		ERR_FIND_ASSIGN,
		ERR_KEY_DUPLICATED,
		OK = 0
	};

	using item_t = std::map< std::string , std::string >;
	using section_t = std::map< std::string , item_t >;
private:
	item_t      m_root_items__;            // 项目
	section_t   m_root_sections__;         // 小节

	std::string m_curr_section__;        // 当前操作的小节名称
private:
	/**
	 * @brief 将数据分割成不同的行，并过滤掉回车符号和换行符号
	 */
	size_t split_line__( wheels::mallocSharedPtr<char>& buff , wheels::ArrayString& rst );
	/**
	 * @brief 解析文件，构造DOM树
	 * @param file[ I ]， ini文件路径
	 * @return 成功操作返回OK , 否则返回错误代码
	 */
	emErrCode parse__( wheels::mallocSharedPtr< char >& buff );
	/**
	 * @brief 处理小节中的变量
	 * @param str[ I ],
	 * @return 成功操作返回OK，否则返回错误代码
	 */
	emErrCode process_item__( const std::string& str );
	/**
	 * @brief 处理小节外的变量
	 * @param str[ I ],
	 * @return 成功操作返回OK，否则返回错误代码
	 */
	emErrCode process_root_item__( const std::string& str );
	/**
	 * @brief 判断是否是注释
	 * @param 
	 * @return 
	 */
	bool is_comment__( const std::string& str );
	/**
	 * @brief 判断是否是小节
	 * @param
	 * @return 是小节返回true
	 */
	bool is_section__( const std::string& str );
	/**
	 * @brief 判断是否是变量
	 * @param
	 * @return 
	 */
	bool is_item__( const std::string& str );
		
public:
	iniRead( const std::string& file );
	~iniRead();

	/**
	 * 获取键值内容
	 * @tparam T 返回值类型的原始类型
	 * @tparam metaType,中间类型，调用std::decay产生，可能会是指针类型
	 * @tparam real_t，最终类型，移除指针后的类型
	 * @return 如果找到键名，返回对应的数据
	 * @exceptions 如果找不到变量则抛出异常
	 */
	template< typename T >
	T get( const std::string& path , const T * p = nullptr /*避免重载冲突*/ )
	{
		using metaType = typename std::decay< T >::type;
		using real_t = typename std::conditional< std::is_pointer< metaType >::value ,
						      typename std::remove_pointer< metaType >::type ,
						      metaType
						      >::type;
							  
		static_assert( std::is_fundamental< real_t >::value || std::is_same< real_t , std::string >::value , "Unsupported data type." );
		real_t ret;

		std::string sec , key;
		size_t pos = path.find("/");
		if( pos != std::string::npos ){
			sec = path.substr( 0 , pos );
			key = path.substr( pos + 1 );
		}

		if( sec.empty() ){
			auto it = m_root_items__.find( key );
			if( it != m_root_items__.end() ){
				std::stringstream ss;
				ss.str( it->second );

				ss >> ret;
			}else{
				throw std::runtime_error( "找不到指定名称的变量" );
			}
		}else{
			auto sit = m_root_sections__.find( sec );
			if( sit == m_root_sections__.end() ) throw std::runtime_error( "找不到小节名" );

			auto it = sit->second.find( key );
			if( it == sit->second.end() ) throw std::runtime_error( "找不到变量名" );

			std::stringstream ss;
			ss.str( it->second );
			ss >> ret;
		}
		return ret;
	}

	/**
	 * @brief 修改值
	 */
	template< typename T >
	void set( const std::string& path , const T& data ){
		static_assert( std::is_fundamental< T >::value || std::is_same< T , std::string >::value , "Unsupported data type." );
		T ret;

		std::string sec , key;
		size_t pos = path.find("/");
		if( pos != std::string::npos ){
			sec = path.substr( 0 , pos );
			key = path.substr( pos + 1 );
		}

		if( sec.empty() ){
			auto it = m_root_items__.find( key );
			std::stringstream ss;
			ss << data;
			if( it != m_root_items__.end() ){
				it->second = ss.str();
			}else{
				m_root_items__.insert( std::make_pair( key , ss.str() ) ); 
			}
		}else{
			std::stringstream ss;
			ss << data;
			
			auto sit = m_root_sections__.find( sec );
			if( sit == m_root_sections__.end() ){
				item_t i;
				i.insert( std::make_pair( key , ss.str() ) );
				m_root_sections__.insert( std::make_pair( sec , i ) );
				return;
			}
			auto it = sit->second.find( key );
			
			if( it != sit->second.end() ){
				it->second = ss.str();
			}else{
				sit->second.insert( std::make_pair( key , ss.str() ) );
			}
		}
	}
	/**
	 * @brief 保存文件
	 * @param file[ I ], 文件名称
	 * @return 成功操作返回true，否则返回false
	 */
	bool save( const std::string& file );

};
}
